﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace Theoreme.Library
{
    public static class ReflectionHelper
    {


        /// <summary>
        /// Determines whether the specified property 
        /// is decorated with the specified attribute type. 
        /// </summary>
        /// <typeparam name="T">The attribute type</typeparam>
        /// <param name="property">The property.</param>
        /// <returns>true if the specified PropertyInfo
        /// is decorated with the specified Attribute type.
        /// </returns>
        public static bool IsDecoratedWith<T>(this PropertyInfo property) where T : Attribute
        {
            // On obtient un tableau d'object que l'on
            // recaste en tableau d'Attribute..
            var attributes = (Attribute[])property.GetCustomAttributes(typeof(T), true);

            return attributes.Length > 0;
        }


        /// <summary>
        /// Determines whether the specified property 
        /// is decorated with the specified attribute type. 
        /// </summary>
        /// <typeparam name="T">The attribute type</typeparam>
        /// <param name="type">The property.</param>
        /// <returns>true if the specified PropertyInfo
        /// is decorated with the specified Attribute type.
        /// </returns>
        public static bool IsDecoratedWith<T>(this Type type) where T : Attribute
        {
            // On obtient un tableau d'object que l'on
            // recaste en tableau d'Attribute..
            var attributes = (Attribute[])type.GetCustomAttributes(typeof(T), true);

            return attributes.Length > 0;
        }


        /// <summary>
        /// Gets the unique attribute of the specified type.
        /// </summary>
        /// <typeparam name="T">The attribute type.</typeparam>
        /// <param name="property">The property.</param>
        /// <returns>The unique attribute.</returns>
        public static T GetUniqueAttribute<T>(this PropertyInfo property) where T : Attribute
        {
            object[] attrs = property.GetCustomAttributes(typeof(T), true);

            // on renvoie le 1er élémént du tableau,
            // re-casté vers le type de l'attribut ..            
            return (T)attrs[0];
        }


        /// <summary>
        /// Gets the unique attribute.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="type">The type.</param>
        /// <returns></returns>
        public static T GetUniqueAttribute<T>(this Type type) where T : Attribute
        {
            object[] attrs = type.GetCustomAttributes(typeof(T), true);

            // on renvoie le 1er élémént du tableau,
            // re-casté vers le type de l'attribut ..            
            return (T)attrs[0];
        }


        /// <summary>
        /// Gets the property value.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        /// <param name="target">The target object.</param>
        public static object GetPropertyValue(string propertyName, object target)
        {
            // on obtient le type de l'objet..
            var type = target.GetType();
            var flags = GetStandardBindings();
            var result = type.InvokeMember(propertyName,
                                              flags | BindingFlags.GetProperty,
                                              null,
                                              target,
                                              null);

            return result;
        }


        /// <summary>
        /// Gets the property value.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        /// <param name="target">The target object.</param>
        public static T GetPropertyValue<T>(string propertyName, object target) where T : class
        {
            // on obtient le type de l'objet..
            var type = target.GetType();
            var flags = GetStandardBindings();
            var result = type.InvokeMember(propertyName,
                                              flags | BindingFlags.GetProperty,
                                              null,
                                              target,
                                              null);

            return result as T;
        }

        /// <summary>
        /// Sets the property value.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        /// <param name="target">The target.</param>
        /// <param name="propertyValue">The property value.</param>
        public static void SetPropertyValue(string propertyName, object target, object propertyValue)
        {
            // on obtient le type de l'objet..
            var type = target.GetType();

            // on constitue une valeur de BindingFlags 
            // pour avoir les éléments :
            //      statiques   +   instance
            //      public      +   non publics
            var flags = GetStandardBindings();


            // et l'on invoque dynamiquement la propriété
            // en écriture, en lui passant la valeur souhaitée
            // sous la forme d'un tableau d'object..
            type.InvokeMember(propertyName,
                                flags | BindingFlags.SetProperty, null,
                                target,
                                new object[] { propertyValue });
        }


        /// <summary>
        /// Gets the standard bindings.
        /// </summary>
        /// <returns></returns>
        private static BindingFlags GetStandardBindings()
        {
            // on constitue une valeur de BindingFlags 
            // pour avoir les éléments :
            //      statiques + instance
            //      public + non publics

            return BindingFlags.Public          |
                        BindingFlags.NonPublic  |
                        BindingFlags.Static     |
                        BindingFlags.Instance;
        }

        /// <summary>
        /// Returns true if the specified <see cref="Type"/> implements
        /// the specified interface.
        /// </summary>
        /// <typeparam name="TInterfaceType">The interface type to check for.</typeparam>
        /// <param name="type">The class to check.</param>
        /// <returns>true if the class to check implements the specified interface.</returns>
        public static bool ImplementsInterface<TInterfaceType>(this Type type)
        {
            var result = false;

            var implementedInterfaces = from x in type.GetInterfaces()
                                        where x == typeof (TInterfaceType)
                                        select x;

            result = (implementedInterfaces.Count() > 0);

            return result;
        }

        /// <summary>
        /// Returns true if the specified object implements
        /// the specified interface.
        /// </summary>
        /// <typeparam name="TInterfaceType">The interface type to check for.</typeparam>
        /// <param name="target">The object to check.</param>
        /// <returns>true if the object to check implements the specified interface.</returns>
        public static bool ImplementsInterface<TInterfaceType>(this object target)
        {
            return ImplementsInterface<TInterfaceType>(target.GetType());
        }


        /// <summary>
        /// Determines whether the specified Type is derived 
        /// from the type of T.
        /// </summary>
        /// <typeparam name="T">The Type</typeparam>
        /// <param name="typeToTest">The type to test.</param>
        /// <returns>
        /// true if the type to test is derived from T,
        /// else, false.
        /// </returns>        
        public static bool IsDerivedFrom<T>(this Type typeToTest)
        {
            var result = false;

            if (!typeToTest.IsInterface)
            {
                while (typeToTest.BaseType != typeof(object))
                {
                    if (typeToTest.BaseType == typeof(T))
                    {
                        result = true;
                        break;
                    }
                    typeToTest = typeToTest.BaseType;
                }
            }
            return result;
        }


        /// <summary>
        /// Determines whether the specified object is derived 
        /// from the type of T.
        /// </summary>
        /// <typeparam name="T">The Type</typeparam>
        /// <param name="objectToTest">The object to test.</param>
        /// <returns>
        /// true if the type to test is derived from T,
        /// else, false.
        /// </returns>        
        public static bool IsDerivedFrom<T>(this object objectToTest)
        {
            return IsDerivedFrom<T>(objectToTest.GetType());
            
        }

        /// <summary>
        /// Determines if the type has a default constructor
        /// </summary>
        /// <param name="Type">Type to check</param>
        /// <returns>True if it does, false otherwise</returns>
        public static bool HasDefaultConstructor(this Type Type)
        {   
            return Type.GetConstructors(BindingFlags.Public | BindingFlags.Instance).Any(x => x.GetParameters().Length == 0);
        }

        /// <summary>
        /// Creates an instance of the type
        /// </summary>
        /// <param name="Type">Type to create an instance of</param>
        /// <param name="args">Arguments sent into the constructor</param>
        /// <returns>The newly created instance of the type</returns>
        public static object Create(this Type Type, params object[] args)
        {            
            return Activator.CreateInstance(Type, args);
        }


    }
}
